/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ThumbsDown.
 *
 * The Initial Developer of the Original Code is
 * Atte Kemppilä.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */


var EXPORTED_SYMBOLS = ["StringArray"];


function StringArray(strings)
{
    this.strings = strings;
}

StringArray.prototype =
{
    get size() { return this.strings.length; },
    
    get hasNull() { if (this._hasNull === undefined) this._hasNull = this.__hasNull(); return this._hasNull; },
    get isEmpty() { if (this._isEmpty === undefined) this._isEmpty = this.__isEmpty(); return this._isEmpty; },
    get length()  { if (this._length  === undefined) this._length  = this.__length();  return this._length;  },
    get equal()   { if (this._equal   === undefined) this._equal   = this.__equal();   return this._equal;   },
    get prefix()  { if (this._prefix  === undefined) this._prefix  = this.__prefix();  return this._prefix;  },
    get suffix()  { if (this._suffix  === undefined) this._suffix  = this.__suffix();  return this._suffix;  },
    
    __hasNull: function()
    {
        return this.strings.some(function(s) { return (s == null); });
    },
    
    __isEmpty: function()
    {
        return (this.length.max == 0);
    },
    
    __length: function()
    {
        var lengths = this.lengths();
        var min = lengths.reduce(function(a, b) { return Math.min(a, b); });
        var max = lengths.reduce(function(a, b) { return Math.max(a, b); });
        return {min: min, max: max};
    },
    
    __equal: function()
    {
        var s = this.strings[0];
        for (var i = 1; i < this.strings.length; i++)
            if (this.strings[i] != s)
                return false;
        return true;
    },
    
    __prefix: function()
    {
        var strings = this.strings;
        var prefix = 0;
        var n = this.length.min;
        loop:
        while (prefix < n)
        {
            var c = strings[0][prefix];
            for (var i = 1; i < strings.length; i++)
                if (strings[i][prefix] != c)
                    break loop;
            prefix++;
        }
        return strings[0].substr(0, prefix);
    },
    
    // note that this calls this.prefix so that prefix and suffix 
    // are consistant regardless the order in which they are called 
    // (but suffix is not neccessaruly the longest possible)
    __suffix: function()
    {
        var strings = this.strings;
        var suffix = 0;
        var n = this.length.min - this.prefix.length;
        loop:
        while (suffix < n)
        {
            var c = strings[0][strings[0].length - suffix - 1];
            for (var i = 1; i < strings.length; i++)
                if (strings[i][strings[i].length - suffix - 1] != c)
                    break loop;
            suffix++;
        }
        return strings[0].substr(strings[0].length - suffix);
    },
    
    split: function(separator, limit)
    {
        return [s.split(separator, limit) for each (s in this.strings)];
    },
    
    substr: function(startIndex, length)
    {
        if (Array.isArray(startIndex))
            return this._substr_array(startIndex, length);
        else
            return this._substr_int(startIndex, length);
    },
    
    _substr_int: function(startIndex, length)
    {
        return new StringArray([String.prototype.substr.apply(s, arguments) for each (s in this.strings)]);
    },
    
    _substr_array: function(startIndex, length)
    {
        var size = this.size;
        if (startIndex.length != size)
            throw new Error("startIndex array size mismatch: " + startIndex.length + ", expected: " + size);
        if (length && (length.length != size))
            throw new Error("length array size mismatch: " + length.length + ", expected: " + size);
        
        var strings = this.strings;
        var array = new Array(size);
        if (length)
            for (var i = 0; i < size; i++)
                array[i] = strings[i].substr(startIndex[i], length[i]);
        else
            for (var i = 0; i < size; i++)
                array[i] = strings[i].substr(startIndex[i]);
        
        return new StringArray(array);
    },
    
    substring: function(startIndex, endIndex)
    {
        if (Array.isArray(startIndex))
            return this._substring_array(startIndex, endIndex);
        else
            return this._substring_int(startIndex, endIndex);
    },
    
    _substring_int: function(startIndex, endIndex)
    {
        return new StringArray([String.prototype.substring.apply(s, arguments) for each (s in this.strings)]);
    },
    
    _substring_array: function(startIndex, endIndex)
    {
        var size = this.size;
        if (startIndex.length != size)
            throw new Error("startIndex array size mismatch: " + startIndex.length + ", expected: " + size);
        if (endIndex && (endIndex.length != size))
            throw new Error("endIndex array size mismatch: " + endIndex.length + ", expected: " + size);
        
        var strings = this.strings;
        var array = new Array(size);
        if (endIndex)
            for (var i = 0; i < size; i++)
                array[i] = strings[i].substring(startIndex[i], endIndex[i]);
        else
            for (var i = 0; i < size; i++)
                array[i] = strings[i].substring(startIndex[i]);
        
        return new StringArray(array);
    },
    
    lengths: function()
    {
        return [s.length for each (s in this.strings)];
    },
    
    middle: function()
    {
        var prefix = this.prefix.length;
        var suffix = this.suffix.length;
        var strings = [s.substring(prefix, s.length - suffix) for each (s in this.strings)];
        return new StringArray(strings);
    },
    
    startsWith: function(sa)
    {
        if (this.size != sa.size)
            throw new Error("string array sizes mismatch");
        
        for (var i = 0; i < this.size; i++)
        {
            var s = sa.strings[i];
            if (this.strings[i].substr(0, s.length) != s)
                return false;
        }
        return true;
    },
    
    test: function(re, every)
    {
        if (every)
            return this.strings.every(RegExp.prototype.test, re);
        else
            return this.strings.some(RegExp.prototype.test, re);
    },
    
    tokenize: function(separator, limit)
    {
        var split = this.split(separator, limit);
        var lengths = [arr.length for each (arr in split)];
        var max = lengths.reduce(function(a, b) { return Math.max(a, b); });
        
        var saArray = [];
        for (var i = 0; i < max; i++)
        {
            var strings = [((i < arr.length) ? arr[i] : null) for each (arr in split)];
            saArray.push(new StringArray(strings));
        }
        
        return saArray;
    },
    
    equals: function(sa)
    {
        if (this.size != sa.size)
            return false;
        
        for (var i = 0; i < this.size; i++)
            if (this.strings[i] != sa.strings[i])
                return false;
        
        return true;
    },
    
    toString: function()
    {
        return "[" + this.strings.join(", ") + "]";
    }
}
